package com.custom.jdbc.executor.convert;

import com.custom.comm.exceptions.CustomCheckException;
import com.custom.comm.utils.CustomUtil;
import com.custom.comm.utils.ReflectUtil;
import com.custom.jdbc.configuration.DbGlobalConfig;
import com.custom.jdbc.executebody.SqlExecutorBody;
import com.custom.jdbc.handler.*;
import lombok.extern.slf4j.Slf4j;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author  Xiao-Bai
 * @since  2022/11/13 22:08
 * 
 */
@SuppressWarnings("unchecked")
public class DefaultTypeMappedHandler extends AbstractRsHandler {


    public DefaultTypeMappedHandler(DbGlobalConfig globalConfig) {
        super(globalConfig);
    }

    public <T> T getTargetObject(SqlExecutorBody executorBody, Class<T> mappedType, ResultSet rs) throws Exception {
        Map<String, Object> resultMap = resToMap(rs);

        // 实例化该对象，前提是需要存在无参构造，否则可能抛出异常
        T instance = ReflectUtil.getInstance(mappedType);
        DefaultMappedCache<T> mappedTargetCache = getMappedTargetCache(mappedType);
        for (Map.Entry<String, Object> entry : resultMap.entrySet()) {

            String label = entry.getKey();
            if (isUnderlineToCamel()) {
                label = CustomUtil.underlineToCamel(label);
            }
            Object value = entry.getValue();
            if (value != null) {
                PropertyCache propertyCache = mappedTargetCache.findForName(label);
                if (propertyCache != null) {
                    try {
                        TypeHandler<?> targetTypeHandler = propertyCache.getTypeHandler();
                        value = targetTypeHandler.getTypeValue(value);
                        if (value != null) {
                            PropertyDescriptor descriptor = propertyCache.getDescriptor();
                            Method writeMethod = descriptor.getWriteMethod();
                            writeMethod.invoke(instance, value);
                        }
                    } catch (IllegalArgumentException | ClassCastException e) {
                        log.error("Type mapped handler setting error, resulting in mismatch with actual value, " +
                                        "target property: {}({}), real value: {}({})",
                                label, propertyCache.getDescriptor().getPropertyType(),
                                value, value.getClass().getSimpleName());
                        throw e;
                    }
                }
            }
        }
        return instance;
    }


    public void setUpdateParam(SqlExecutorBody executorBody, PreparedStatement statement) throws Exception {
        setQueryParam(executorBody, statement);
    }



    /**
     * 映射对象缓存
     */
    protected static final Map<Class<?>, WeakReference<DefaultMappedCache<?>>> OBJECT_HANDLE_CACHE = new ConcurrentHashMap<>();
    protected <T> DefaultMappedCache<T> getMappedTargetCache(Class<T> mappedType) {
        WeakReference<DefaultMappedCache<?>> weakTargetCache = OBJECT_HANDLE_CACHE.get(mappedType);
        if (weakTargetCache == null || weakTargetCache.get() == null) {
            DefaultMappedCache<T> mappedTargetCache = new DefaultMappedCache<>(mappedType);
            OBJECT_HANDLE_CACHE.put(mappedType, new WeakReference<>(mappedTargetCache));
            return mappedTargetCache;
        }
        return (DefaultMappedCache<T>) weakTargetCache.get();
    }


    @Slf4j
    public static class DefaultMappedCache<T> {
        private final Map<String, PropertyCache> propertyCacheMap;

        public DefaultMappedCache(Class<T> targetClass) {
            this.propertyCacheMap = new HashMap<>();

            try {
                List<PropertyDescriptor> descriptorList = ReflectUtil.getProperties(targetClass);
                for (PropertyDescriptor descriptor : descriptorList) {
                    PropertyCache propertyCache = new PropertyCache(descriptor);
                    this.propertyCacheMap.put(descriptor.getName(), propertyCache);
                }
            } catch (IntrospectionException e) {
                log.error(e.toString(), e);
            }

        }

        public PropertyCache findForName(String fieldName) {
            return propertyCacheMap.get(fieldName);
        }

    }

}
